package com.trade.dao.util;

import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.text.*;

import com.trade.data.HistoricalDataVO;
import com.trade.data.EquityIntradayDataVO;
import com.trade.data.type.MovingAveragePeriodTypes;
import com.trade.data.calculation.MovingAverageValuesVO;
import com.trade.data.calculation.MACDValuesVO;
import com.trade.data.calculation.RSIValuesVO;

import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics;

public class TechnicalCalculationUtil {


	private static DecimalFormat decimalFormat = new DecimalFormat("#.00");

	public static synchronized Double formatDouble(Double value){
		return value != null ? Double.valueOf(decimalFormat.format(value)) : 0 ;
	}


	/**
	 * This method used to calculate EMA
	 *
	 * @param pHistoricalDataVO
	 * @param days
	 * @param previousEMA
	 * @return
	 */
	public static synchronized Double calculateEMA(
			HistoricalDataVO pHistoricalDataVO, double days, double previousEMA) {
		Double k = (double) (2 / (days + 1));
		return k.doubleValue() * (pHistoricalDataVO.getClose() - previousEMA)
				+ previousEMA;
	}



	/* Calculate Standard Deviation */

	public static synchronized Double getStandardDeviation(List<EquityIntradayDataVO> list , int value){
		DescriptiveStatistics descriptiveStatistics  =new DescriptiveStatistics ();
		try{
			if( list !=null && list.size() >= value ){
				for(int i = list.size() - 1 , j = 1 ; i > 0 ; i--,j++){
						EquityIntradayDataVO dataVO = (EquityIntradayDataVO)list.get(i);
						descriptiveStatistics.addValue( dataVO.getClose());
						if( j == value ){
							return descriptiveStatistics.getStandardDeviation() ;
						}
				}
			}

		}catch(Exception e){
			System.out.println("getStandardDeviation()-"+e.getMessage());
		}

		return descriptiveStatistics.getStandardDeviation() ;
	}





	public static synchronized Double getStandardDeviationForTypicalPrice(List<MovingAverageValuesVO> list , int value , Double sma20TypicalPrice){
		DescriptiveStatistics descriptiveStatistics  =new DescriptiveStatistics ();
		try{

			if( list !=null && list.size() >= value ){
				for(int i = list.size() - 1 , j = 1 ; i > 0 ; i--,j++){
						MovingAverageValuesVO dataVO = (MovingAverageValuesVO)list.get(i);
						if( dataVO.getTypicalPrice() != null){
								descriptiveStatistics.addValue( formatDouble ( sma20TypicalPrice - dataVO.getTypicalPrice() ) );

							if( j == value ){
								return descriptiveStatistics.getStandardDeviation() ;
							}
						}
				}
			}

		}catch(Exception e){
			System.out.println("getStandardDeviationForTypicalPrice()-"+e.getMessage());
		}

		return descriptiveStatistics.getStandardDeviation() ;
	}




	//------------------- Esper function ------------------------


	// RSI Calcualtions

	/**
	 * This method used to calculate average 14 days Gain or Loss
	 */



	public static synchronized Double calculateAvgGainOrLossRSI(
			Map<String, List<Double>> equityIntradayHistoricalDataMap,
			HistoricalDataVO historicalDataVO) {
		Double average = 0d ;
		if (equityIntradayHistoricalDataMap.containsKey(historicalDataVO
				.getSymbol())) {
			List<Double> gainOrLossList = (List<Double>) equityIntradayHistoricalDataMap
					.get(historicalDataVO.getSymbol());

			List<Double> tempGainOrLossList = new LinkedList<Double>();
			tempGainOrLossList.addAll(gainOrLossList);

			Collections.reverse(tempGainOrLossList);
			int i = 0;
			for (Double value : tempGainOrLossList) {
				if (i == 13) {
					break;
				}

				average += value ;
				i++;
			}

		}
		return average / 14 ;
	}

	/**
	 * This method used to calculate average 14 days Gain or Loss
	 */
	public static synchronized Double getLastGainOrLossRSI(
			Map<String, List<Double>> equityIntradayHistoricalDataMap,
			HistoricalDataVO historicalDataVO) {
		Double lastAvg = 0d ;
		if (equityIntradayHistoricalDataMap.containsKey(historicalDataVO
				.getSymbol())) {
			List<Double> gainOrLossList = (List<Double>) equityIntradayHistoricalDataMap
					.get(historicalDataVO.getSymbol());

			List<Double> tempGainOrLossList = new LinkedList<Double>();
			if( gainOrLossList != null){
				tempGainOrLossList.addAll(gainOrLossList);
				Collections.reverse(tempGainOrLossList);
			}
			lastAvg = tempGainOrLossList.get(0);
		}

		return lastAvg;
	}

	/**
	 * This method used to calculate RSI
	 * @param equityGainIntradayHistoricalDataMap
	 * @param equityLossIntradayHistoricalDataMap
	 * @param equityHistoricalDataVO
	 * @return
	 */
	public static synchronized Double calculateRSI(Map<String, List<Double>> equityGainIntradayHistoricalDataMap , Map<String, List<Double>> equityLossIntradayHistoricalDataMap, HistoricalDataVO equityHistoricalDataVO) {
		Double averageGain ;
		Double averageLoss;

		averageGain= TechnicalCalculationUtil.calculateAvgGainOrLossRSI(equityGainIntradayHistoricalDataMap, equityHistoricalDataVO);
		averageLoss= TechnicalCalculationUtil.calculateAvgGainOrLossRSI(equityLossIntradayHistoricalDataMap, equityHistoricalDataVO);

		Double gain = TechnicalCalculationUtil.getLastGainOrLossRSI(equityGainIntradayHistoricalDataMap,equityHistoricalDataVO);
		Double loss = TechnicalCalculationUtil.getLastGainOrLossRSI(equityLossIntradayHistoricalDataMap,equityHistoricalDataVO);

		averageGain = averageGain * 13 + gain / 14 ;
		averageLoss = averageLoss * 13 + loss / 14 ;

		return (100-(100/(1+ (averageGain / averageLoss ))));
	}


	/**
	 * This method used to calculate MACD Line value
	 * @param emaIntradayHistoricalDataMap
	 * @param equityHistoricalDataVO
	 * @return
	 */
	public static synchronized Double calculateMACD( Map<String, Map<Integer, HistoricalDataVO>> emaIntradayHistoricalDataMap , HistoricalDataVO equityHistoricalDataVO ){

		Double macdLine = 0d;

		if( emaIntradayHistoricalDataMap.containsKey(equityHistoricalDataVO.getSymbol())){
			Map<Integer,HistoricalDataVO> map = (Map<Integer,HistoricalDataVO>)emaIntradayHistoricalDataMap.get(equityHistoricalDataVO.getSymbol());
			HistoricalDataVO ema26 = map.get(MovingAveragePeriodTypes.MA_26.getId());
			HistoricalDataVO ema12 = map.get(MovingAveragePeriodTypes.MA_12.getId());
			if( ema26 != null && ema12 != null){
				macdLine = ema12.getCalValue() - ema26.getCalValue();
			}
		}
		return macdLine;
	}

	/**
	 * This method return EMA value from Map
	 * @param emaIntradayHistoricalDataMap
	 * @param equityHistoricalDataVO
	 * @param ema
	 * @return
	 */
	public static synchronized Double getEMAValue( Map<String, Map<Integer, HistoricalDataVO>> emaIntradayHistoricalDataMap , HistoricalDataVO equityHistoricalDataVO , Integer ema ){

		Double emaValue = 0d;

		if( emaIntradayHistoricalDataMap.containsKey(equityHistoricalDataVO.getSymbol())){
			Map<Integer,HistoricalDataVO> map = (Map<Integer,HistoricalDataVO>)emaIntradayHistoricalDataMap.get(equityHistoricalDataVO.getSymbol());
			HistoricalDataVO emaDataVO = map.get(ema);
			emaValue = emaDataVO.getCalValue();
		}
		return emaValue;
	}


	public static synchronized Double calculateReverseAvgInList(
			Map<String, List<Double>> equityIntradayHistoricalDataMap,
			HistoricalDataVO historicalDataVO , Integer averageCount) {

		Double average = 0d ;
		if (equityIntradayHistoricalDataMap.containsKey(historicalDataVO
				.getSymbol())) {
			List<Double> gainOrLossList = (List<Double>) equityIntradayHistoricalDataMap
					.get(historicalDataVO.getSymbol());

			List<Double> tempGainOrLossList = new LinkedList<Double>();
			tempGainOrLossList.addAll(gainOrLossList);

			Collections.reverse(tempGainOrLossList);
			int i = 0;
			for (Double value : tempGainOrLossList) {
				if (i == averageCount - 1) {
					break;
				}

				average += value ;
				i++;
			}

		}
		return average / averageCount ;
	}


	/**
	*	%K = (Current Close - Lowest Low)/(Highest High - Lowest Low) * 100
	*
	*	%D = 3-day SMA of %K
	*/

	public static Double calculateSOKPercent(Double currentClose , Double highestHigh , Double lowestLow){

		return formatDouble( ( currentClose - lowestLow ) / ( highestHigh - lowestLow ) * 100 );
	}



}
